/*
 *     Copyright 2006 Robert Burrell Donkin
 *  
 *   Licensed under the Apache License, Version 2.0 (the "License");
 *   you may not use this file except in compliance with the License.
 *   You may obtain a copy of the License at
 *  
 *   http://www.apache.org/licenses/LICENSE-2.0
 *  
 *   Unless required by applicable law or agreed to in writing, software
 *   distributed under the License is distributed on an "AS IS" BASIS,
 *   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *   See the License for the specific language governing permissions and
 *   limitations under the License.
 */

package rat.element;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Arrays;
import java.util.Collection;
import java.util.regex.Pattern;

import rat.element.comparator.ComparatorFactory;

/**
 * Contains elements.
 */
public class Directory implements IElementContainer {

    protected final File file;
    protected final String name;

    private final Pattern ignoreNameRegex;
	
	public Directory(File file) {
            this(file, null);
	}
	
    public Directory(File file, final Pattern ignoreNameRegex) {
        this(file.getPath(), file, ignoreNameRegex);
    }

    /** for restricted directory only */
    protected Directory(final String name) {
        this(name, null, null);
    }

    private Directory(final String name, final File file, final Pattern ignoreNameRegex) {
        this.name = name;
        this.file = file;
        this.ignoreNameRegex = ignoreNameRegex;
    }
    
	public final Collection getAllContainedElements() {
	    final CollectingElementConsumer consumer = new CollectingElementConsumer();
        consumeContents(consumer);
        final Collection collectedElements = consumer.getCollectedElements();
        return collectedElements;
	}
	
    public boolean isRestricted() {
        return false;
    }

    protected final boolean ignored(final String name) {
        boolean result = false;
        if (ignoreNameRegex != null) {
            result = ignoreNameRegex.matcher(name).matches();
        }
        return result;
    }

	/**
	 * Guesses whether the given file is a note.
	 * @param file <code>File</code>, not null 
	 * @return true if the file is a note, false otherwise
	 */
	boolean isNote(final File file) {
		final String name = file.getName();
		return isNote(name);
	}

    protected static final boolean isNote(final String name) {
        return NoteElement.isNote(name);
    } 

	/**
	 * Guesses whether the given file is binary.
     * Note that binary is used in a broad but technical sense:
     * files with structure contents which cannot be commented.
	 * @param file <code>File</code>, not null 
	 * @return true if the file is likely to be binary, false otherwise
	 */
	boolean isBinary(final File file) {
            InputStream in = null;
            try {
                return isBinary(file.getName())
                    || (!BinaryElement.isNonBinary(file.getName())
                        && isBinary(in = new FileInputStream(file)));
            } catch (IOException e) {
                return false;
            } finally {
                if (in != null) {
                    try {in.close();} catch (IOException ex) { /* swallow */}
                }
            }
        }

    protected final boolean isBinary(InputStream in) {
        return BinaryElement.isBinary(in);
	}

    protected static final boolean isBinary(final String name) {
        return BinaryElement.isBinary(name);
    }
	
    static final boolean isBinaryData(final String name) {
        return BinaryElement.isBinaryData(name);
	}
    
    static final boolean isExecutable(final String suffix, final String name) {
        return BinaryElement.isExecutable(name);
    }
	
    static final boolean isKeystore(final String name) {
        return BinaryElement.isKeystore(name);
	}
	
    static final boolean isImage(final String name) {
        return ArchiveElement.isArchive(name);
	}
	
	boolean isArchive(final File file) {
		final String name = file.getName();
		return isArchive(name);
	}

    static final boolean isBytecode(final String name) {
        return BinaryElement.isBytecode(name);
	}
	
	protected static final boolean isArchive(final String name) {
            return ArchiveElement.isArchive(name);
	}
	
	static String suffix(final String str) {
		String result = "";
		final int lastDot = str.lastIndexOf('.');
		final int length = str.length();
		if (lastDot >= 0  && lastDot < length) {
			result = str.substring(lastDot + 1, length);
		}
		return result;
	}
	
	boolean isRestricted(File file) {
		String name = file.getName();
		boolean result = name.startsWith(".");
		return result;
	}

    public final String getName() {
        return name;
    }
    
	public String toString() {
		return prefix() + getName();
	}

	protected String prefix() {
		return "D       ";
	}

    public void consumeContents(IElementConsumer consumer) {
        final File[] files = file.listFiles();
        Arrays.sort(files, ComparatorFactory.fileNameComparator());
        if (files != null) {
            // bredth first traversal
            for (int i = 0; i < files.length; i++) {
                final File file = files[i];
                final String name = file.getName();
                if (!ignored(name)) {
                    if (!file.isDirectory()) {
                        consumeDocument(consumer, file);
                    }
                }
            }
            for (int i = 0; i < files.length; i++) {
                final File file = files[i];
                final String name = file.getName();
                if (!ignored(name)) {
                    if (file.isDirectory()) {
                        processDirectory(consumer, file);
                    }
                }
            }
        }
    }

    private void processDirectory(IElementConsumer consumer, final File file) {
        if (!isRestricted(file)) {
            Directory directory = new Directory(file, ignoreNameRegex);
            directory.consumeContents(consumer);
        }
    }

    private void consumeDocument(IElementConsumer consumer, final File file) {
        IElement element = null;
        ElementTypeEnum guess =
            ElementTypeEnum.guessType(file.getName());
        if (guess == ElementTypeEnum.NOTE) {
            element = new NoteElement(file);
        }
        if (guess == ElementTypeEnum.ARCHIVE) {
            element = new ArchiveElement(file);
        }
        if (guess == ElementTypeEnum.BINARY) {
            element = new BinaryElement(file);
        }
        if (guess == ElementTypeEnum.STANDARD_CONTENT) {
            element = new StandardContentElement(file);
        }
        if (element == null) {
            InputStream in = null;
            try {
                if (BinaryElement
                    .isBinary(in = new FileInputStream(file))) {
                    element = new BinaryElement(file);
                }
            } catch (IOException exe) {
                // swallow
            } finally {
                if (in != null) {
                    try {
                        in.close();
                    } catch (IOException exe) {
                        // swallow
                    }
                }
            }
        }
        if (element == null) {
            element = new StandardContentElement(file);
        }
        consumer.consume(element);
    }
}
